/*  Copyright © 2009 Blake Hegerle -*- C++ -*- */

/*  This file is part of metal.
 
    Metal is free software: you can redistribute it and/or modify it
    under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
    
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU
    General Public License for more details.
 
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see
    <http://www.gnu.org/licenses/>.

*/

/*
  The expr_impl class is a key part of the AST for MP programs. The
  design is based on two things: my experience designing expression
  classes for other projects, and my personal preference that AST
  nodes be entirely immutable. 
  
  The expr_impl are constructed from the bottom up: since type info
  comes from the top down, we cannot assume the type of an expression
  when it is constructed. Add to this the way that identifiers are
  handled: every mention of "x" initialy refers to the same object (an
  expression<ident_expr<meta_x>>). The type information for an
  expression _is_ available statically; we "imbue" that type to the
  expression at runtime. Since the expr_impl is immutable, imbueing
  actually creates a copy of the expr_impl to actually carry the type
  info.

  The expr_impl is the only class that really matters: its various
  subclasses are simply a neat way of organizing the emission
  functions and constructors. Since they add so little value, the
  subclasses could really be removed - this would leave the code in
  expression.hpp with the responsibility of selecting the correct
  constructor and also with passing in a e_type_code (doesn't exist);
  then expr_impl's emit method would have a big switch statement.
*/

#pragma once

#include <ostream>
#include <iostream>
#include <vector>
#include <algorithm>
#include <memory>
#include <type_traits>
#include <functional>

#include "util.h"
#include "forward.h"
#include "assignment.h"
#include "type_code.h"

namespace metal {
  class expr_impl {
    typedef std::function<void (const expr_impl &expr, driver::idriver &)> emission_func;
  public:
    expr_impl(const std::string &name,
	      const std::vector<param_impl> &params,
	      const std::vector<expr_ptr> &children,
	      const std::vector<assign_ptr> &bindings,
	      type_ptr type,
	      const emission_func &emitter = emission_func());

    expr_impl(const std::string &name,
	      type_ptr type,
	      const emission_func &emitter)
      : n(name), t(type), emitter(emitter)
    {}

    virtual bool operator==(const expr_impl &e) const;
    void emit(driver::idriver &drv) const;

    expr_ptr child(int i) const { return c[i]; }
    assign_ptr binding(int i) const { return b[i]; }
    const std::string &name() const { return n; }
    type_ptr type() const { return t; }
    
    const emission_func emitter;

    bool is_ident() const { return n.length() > 0 && c.size() == 0; }
    bool is_lambda() const { return p.size(); }

    const std::vector<param_impl> &params() const { return p; }
    const std::vector<expr_ptr> &children() const { return c; }
    const std::vector<assign_ptr> &bindings() const { return b; }
  protected:
    expr_impl(emission_func emitter, const std::string &name) 
      : emitter(emitter), n(name) { }

    expr_impl(emission_func emitter, const std::string &name, 
	      std::initializer_list<expr_ptr> children)
      : emitter(emitter), n(name), c(children)
    {}

    expr_impl(emission_func emitter, type_ptr type) 
      : emitter(emitter), t(type) 
    {}

    template<typename I>
    expr_impl(emission_func emitter, const std::string &name, I begin, I end)
      : emitter(emitter), n(name), c(begin, end)
    {}

    template<typename I>
    expr_impl(emission_func emitter, I begin, I end)
      : emitter(emitter), c(begin, end)
    {}

    expr_impl(emission_func emitter, std::initializer_list<expr_ptr> children)
      : emitter(emitter), c(children)
    {}

    template<typename I>
    expr_impl(emission_func emitter, std::initializer_list<expr_ptr> children,
	      I begin, I end)
      : emitter(emitter), c(children), b(begin, end)
    {}

    expr_impl(emission_func emitter, std::initializer_list<expr_ptr> children,
	      std::initializer_list<assign_ptr> bindings)
      : emitter(emitter), c(children), b(bindings)
    {}

    template<typename I>
    expr_impl(emission_func emitter, type_ptr type, I begin, I end)
      : emitter(emitter), t(type), b(begin, end)
    {}

    expr_impl(emission_func emitter, expr_ptr expr, type_ptr type)
      : emitter(emitter), c({ expr }), t(type)
    {}

    template<typename I>
    expr_impl(emission_func emitter, expr_ptr expr, I begin, I end)
      : emitter(emitter), c({ expr }), p(begin, end)
    {}
  private:
    const std::string n;
    const std::vector<expr_ptr> c;
    const std::vector<assign_ptr> b;
    const std::vector<param_impl> p;
    type_ptr t;
  };

  class ident_expr_impl : public expr_impl {
  public:
    ident_expr_impl(const std::string &name) 
      : expr_impl(emitter, name) {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class binary_expr_impl : public expr_impl {
  public:
    binary_expr_impl(const char *op, expr_ptr left, expr_ptr right)
      : expr_impl(emitter, op, { left, right })
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class unary_expr_impl : public expr_impl {
  public:
    unary_expr_impl(char op, expr_ptr expr)
      : expr_impl(emitter, std::string(1, op), { expr })
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class constant_expr_impl : public expr_impl {
  public:
    constant_expr_impl(char val);
    constant_expr_impl(signed char val);
    constant_expr_impl(unsigned char val);
    constant_expr_impl(short val);
    constant_expr_impl(unsigned short val);
    constant_expr_impl(int val);
    constant_expr_impl(unsigned int val);
    constant_expr_impl(long val);
    constant_expr_impl(unsigned long val);
    constant_expr_impl(long long val);
    constant_expr_impl(unsigned long long val);
    constant_expr_impl(float val);
    constant_expr_impl(double val);
    constant_expr_impl(long double val);
  };

  class call_expr_impl : public expr_impl {
  public:
    template<typename I>
    call_expr_impl(I begin, I end)
      : expr_impl(emitter, begin, end)
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class dot_expr_impl : public expr_impl {
  public:
    dot_expr_impl(expr_ptr base, const std::string &name)
      : expr_impl(emitter, name, { base })
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);

    friend class expr_impl;
  };

  class subscript_expr_impl : public expr_impl {
  public:
    subscript_expr_impl(expr_ptr base, expr_ptr index)
      : expr_impl(emitter, { base, index })
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class cond_expr_impl : public expr_impl {
  public:
    cond_expr_impl(expr_ptr pred_expr, expr_ptr then_expr, expr_ptr else_expr)
      : expr_impl(emitter, { pred_expr, then_expr, else_expr })
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class map_expr_impl : public expr_impl {
  public:
    template<typename I>
    map_expr_impl(expr_ptr expr, I begin, I end)
      : expr_impl(emitter, { expr }, begin, end)
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class eval_expr_impl : public expr_impl {
  public:
    template<typename I>
    eval_expr_impl(expr_ptr expr, I begin, I end)
      : expr_impl(emitter, { expr }, begin, end)
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class array_expr_impl : public expr_impl {
  public:
    template<typename I>
    array_expr_impl(I begin, I end) 
      : expr_impl(emitter, begin, end)
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class filter_expr_impl : public expr_impl {
  public:
    filter_expr_impl(expr_ptr expr,
		     assign_ptr range, 
		     expr_ptr filter)
      : expr_impl(emitter, { expr, filter }, { range })
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class cons_expr_impl : public expr_impl {
  public:
    template<typename I>
    cons_expr_impl(type_ptr type, I begin, I end)
      : expr_impl(emitter, type, begin, end)
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  class cast_expr_impl : public expr_impl {
  public:
    cast_expr_impl(type_ptr type, expr_ptr expr)
      : expr_impl(emitter, expr, type)
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };

  template<typename PT>
  expr_ptr cnst_impl(PT c) {
    return expr_ptr(new constant_expr_impl(c));
  }

  class lambda_expr_impl : public expr_impl {
  public:
    lambda_expr_impl(expr_ptr expr, const std::vector<param_impl> &params)
      : expr_impl(emitter, expr, params.begin(), params.end())
    {}
  private:
    static void emitter(const expr_impl &expr, driver::idriver &d);
  };
}
